module Othello where

import Maybe
import Tablero
import Char
import List

data Juego = J Color Tablero
data Jugada = M Posicion | Paso
  deriving (Show, Eq)

data Arbol a = Nodo a [Arbol a] deriving Show
type ArbolJugadas = Arbol ([Jugada], Juego)

type Valuacion = Juego -> Double

instance Show Juego where
  show (J turno tablero) = "\n--Juega el " ++ show turno ++ "--\n" ++ show tablero

arbolDeJugadas :: Juego -> ArbolJugadas
arbolDeJugadas j = Nodo ([], j) $ zipWith agmov movs hijos
  where agmov m (Nodo (ms, r) hs) = Nodo ((m:ms), r) (map (agmov m) hs)
        movsJuegos = jugadasPosibles j
        movs = map fst movsJuegos
        hijos = map (arbolDeJugadas . snd) movsJuegos

------ Comienza solucion

-- Ej. 7

{-
  Si se recibe una jugada inválida, devuelve Nothing. En caso contrario
  devuelve (Just) el resultado de aplicar la jugada.
-}
jugar :: Jugada -> Juego -> Maybe Juego
jugar jugada juego | esValida juego jugada = Just (hacerJugada juego jugada)
                   | otherwise             = Nothing

{- Verifica si una jugada es válida. Para jugadas Paso siempre da True. -}
esValida :: Juego -> Jugada -> Bool
esValida juego Paso = True
esValida juego (M pos) | (not.enRango) pos = False
                       | otherwise         = movimientoValido juego pos

{-
  Devuelve True si jugar en la posición indicada es válido.
  Asume que la posición está dentro del rango válido.
-}
movimientoValido :: Juego -> Posicion -> Bool
movimientoValido (J color t) pos = isNothing (contenido pos t)
                                 && (not.null) (posicionesAInvertir pos (poner pos color t))

{- Aplica una jugada asumiendo que es válida. -}
hacerJugada :: Juego -> Jugada -> Juego
hacerJugada juego Paso    = pasar juego 
hacerJugada juego (M pos) = hacerMovimiento juego pos

{- Aplica una jugada de Paso. -}
pasar :: Juego -> Juego
pasar (J color t) = J (colorContrario color) t

{- Aplica un movimiento. -}
hacerMovimiento :: Juego -> Posicion -> Juego
hacerMovimiento (J color t) pos = J (colorContrario color) (siguiente pos color t)

{- Devuelve el Tablero resultante de realizar sobre otro un movimiento. -}
siguiente :: Posicion -> Color -> Tablero -> Tablero
siguiente pos color t = invertirTodas (posicionesAInvertir pos nuevoTablero) nuevoTablero
          where nuevoTablero = poner pos color t


-- Ej. 8
{-
  Devuelve la lista de jugadas posibles que el jugador actual puede realizar
  junto con el Juego resultante de realizar cada jugada.
  Las jugadas posibles son los movimientos posibles, o si no hay movimientos
  posibles la lista con "paso" como único elemento.
-}
jugadasPosibles :: Juego -> [(Jugada, Juego)]
jugadasPosibles juego | (null.movimientosValidos) juego = [armarTupla Paso]
                      | otherwise = map (\mov -> armarTupla (M mov)) (movimientosValidos juego)
  where
    armarTupla jugada  = (jugada, hacerJugada juego jugada)

{- Devuelve todas las posiciones válidas de un tablero. -}
todasLasPosiciones :: [Posicion]
todasLasPosiciones = [(x, y) | x <- columnas, y <- filas]

{- Devuelve las posiciones del tablero en las que el jugador actual puede mover. -}
movimientosValidos :: Juego -> [Posicion]
movimientosValidos juego = filter (movimientoValido juego) todasLasPosiciones

-- Ej. 9

{-
  Función de recursión sobre árboles.
  El tercer argumento es el árbol a recorrer.
  El segundo argumento es una función que, dada la lista de aplicaciones
  recursivas de esta función a los hijos del nodo actual, genera un resultado
  de cualquier tipo "b".
  El primer argumento toma el contenido del nodo actual, el "b" resultante
  de los hijos, y devuelve el valor definitivo de la recursión.
-}
foldArbol :: (a -> b -> c) -> ([c] -> b) -> Arbol a -> c
foldArbol f g (Nodo elem hijos) = f elem (g (map (foldArbol f g) hijos))

-- Ej. 10

{-
  Toma un 'n' y un árbol (posiblemente infinito) y lo poda de manera que devuelve
  un árbol de altura 'n' que corresponde a la parte superior del árbol original.
-}
podar :: Int -> Arbol a -> Arbol a
podar = flip podar'
  where podar' = foldArbol (\a rs n -> if n == 1 then (Nodo a [])
                              else (Nodo a [f (n-1) | f <- rs])) id

-- Ej. 11

-- Representa un camino en el árbol de jugadas junto a su valuación
type Choice = (Double, [Jugada])
-- Representa un nodo del árbol de jugadas
type NodoJugada = ([Jugada], Juego)

{-
  Devuelve la jugada más conveniente para el jugador actual, según Minimax.
-}
mejorJugada :: Valuacion -> ArbolJugadas -> Jugada
mejorJugada val arbol = head (snd (minimax val arbol))

{-
  Recorre el árbol de jugadas acumulando el mejor camino que se encuentra
  en el árbol junto a su valuación.
-}
minimax :: Valuacion -> ArbolJugadas -> Choice
minimax val arbol = foldArbol (fMinimax val) (gMinimax val) arbol

fMinimax :: Valuacion -> NodoJugada -> Maybe Choice -> Choice 
fMinimax val (traza, juego) Nothing                       = (val juego, traza)
fMinimax val (traza, juego) choiceHijos | val juego ==  1 = (val juego, traza)
                                        | val juego == -1 = (val juego, traza)
                                        | otherwise       = invChoice (fromJust choiceHijos)
  where
    invChoice (valor, traza) = (-valor, traza)

gMinimax :: Valuacion -> [Choice] -> Maybe Choice
gMinimax val [] = Nothing
gMinimax val jugadas = Just (foldr1 minChoice jugadas)

{- Devuelve el mínimo entre dos Choices de acuerdo a su valuación. -}
minChoice :: Choice -> Choice -> Choice
minChoice ch1@(val1, _) ch2@(val2, _) | val1 < val2 = ch1
                                      | otherwise   = ch2

-- Ej. 12

{- Devuelve el color del ganador o Nothing si no está definido. -}
ganador :: Juego -> Maybe Color
ganador juego | (null.movimientosValidos) juego && (null.movimientosValidos) (pasar juego)
                  = Just (jugadorConMasFichas juego)
              | otherwise = Nothing

{- 
  Devuelve el jugador con más fichas en el juego.
  Si tienen la misma cantidad, se devuelve el del turno actual.
-}
jugadorConMasFichas :: Juego -> Color
jugadorConMasFichas juego | propias < ajenas = (colorContrario.jugadorActual) juego
                          | otherwise        = jugadorActual juego
  where propias = contarFichasPropias juego
        ajenas  = contarFichasAjenas  juego

{- Devuelve la cantidad de fichas del color indicado. -}
contarFichas :: Juego -> Color -> Int
contarFichas juego color = length (filter esDelColor todasLasPosiciones)
  where
    esDelColor pos = contenido pos (tableroActual juego) == Just color

contarFichasPropias :: Juego -> Int
contarFichasPropias juego = contarFichas juego (jugadorActual juego)

contarFichasAjenas :: Juego -> Int
contarFichasAjenas juego = contarFichas juego ((colorContrario.jugadorActual) juego)

-- Ej. 13

{- Valuación utilizada para la Maquina. -}
valuacionOthello :: Valuacion
valuacionOthello juego | ganador juego == Nothing = valuacionBasica juego
                       | ganador juego == Just (jugadorActual juego) = 1
                       | otherwise = -1

{- Valuación que sólo se basa en la cantidad de fichas. Útil para testing. -}
valuacionBasica :: Valuacion
valuacionBasica juego = 2 * (propias / (propias + ajenas)) - 1
  where
    propias = fromIntegral (contarFichasPropias juego)
    ajenas  = fromIntegral (contarFichasAjenas  juego)
    
-- Auxiliares

{- Observadores de Juego -}
jugadorActual :: Juego -> Color
jugadorActual (J color _) = color

tableroActual :: Juego -> Tablero
tableroActual (J _ tablero) = tablero

{- Juego inicial -}
nuevoJuego :: Juego
nuevoJuego = J Negro tableroInicial

